package com.dxf.redis.distributedlock;

import com.google.common.io.CharStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * @author duxvfeng
 *
 * 改进redis分布式锁，可重入锁
 */
public class ReentrantRedisLock implements Lock{
    private static final Logger log = LoggerFactory.getLogger(ReentrantRedisLock.class);


    private static final ThreadLocal<Jedis> jedisThreadLocal = ThreadLocal.withInitial(() -> {
        Jedis jedis = new Jedis("192.168.10.113", 6379);
        jedis.connect();
        return jedis;
    });

    public static final long TTL=10000;


    private ScheduledThreadPoolExecutor scheduledExecutorService = new ScheduledThreadPoolExecutor(1);

    private static final String key = "dis-lock";

    private static final String lockLua = "reentrant_redis_lock.lua";

    private static final String unlockLua = "reentrant_redis_unlock.lua";

    private static final String watcher ="reentrant_redis_watcher.lua";

    public ReentrantRedisLock() {


    }

    @Override
    public boolean lock(String uuid) {


        boolean flag = false;
        Jedis jedis = jedisThreadLocal.get();
        Long eval = (Long)jedis.eval(getLuaScript(lockLua),
                Arrays.asList(new String[]{key}),
                Arrays.asList(new String[]{uuid,String.valueOf(TTL)}));
        if(eval==1L) return true;
        return flag;
    }

    @Override
    public boolean lock(String uuid,long exTime) {

        /*boolean flag = false;
        long end = System.currentTimeMillis()+exTime;
        while(System.currentTimeMillis()<end){ //自选等待去获取锁
            Jedis jedis = jedisThreadLocal.get();
            Long eval = (Long)jedis.eval(getLuaScript(lockLua),
                    Arrays.asList(new String[]{key}),
                    Arrays.asList(new String[]{uuid,String.valueOf(TTL)}));

            if(eval==1L) { //获取锁成功后 开启看门狗 给分布式锁续命
                this.watcher(uuid);
                return true;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return flag;*/

        return acquireLock(uuid,exTime);
    }

    @Override
    public boolean unlock(String uuid) {
        try(Jedis jedis = jedisThreadLocal.get()){
            Long eval = (Long)jedis.eval(getLuaScript(unlockLua),
                    Arrays.asList(new String[]{key}),
                    Arrays.asList(new String[]{uuid}));
            if(eval==0L) {
                if(jedis !=null){
                    jedis.close();
                }
                return false;
            }
            return true;
        }
    }


    protected boolean acquireLock(String uuid, long ttl){
        try (Jedis jedis = jedisThreadLocal.get()) {
            boolean acquired = false;
            long end = System.currentTimeMillis() + ttl;
            while (System.currentTimeMillis() < end) {
                Long eval = (Long) jedis.eval(getLuaScript(lockLua),
                        Collections.singletonList(key),
                        Arrays.asList(uuid, String.valueOf(ttl)));
                if (eval == 1L) {
                    acquired = true;
                    this.watcher(uuid);
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            return acquired;
        }
    }


    /**
     * 看门狗 ，为即将过期的key 续命
     */
    protected void watcher(String uuid){


        if (scheduledExecutorService.isShutdown()) {
            scheduledExecutorService=new ScheduledThreadPoolExecutor(1);
        }

        scheduledExecutorService.
                scheduleAtFixedRate(new ReentrantRedisLock.WatcherThread(scheduledExecutorService,
                                Arrays.asList(new String[]{key}),
                                Arrays.asList(new String[]{uuid,String.valueOf(TTL)})),
                        1,2, TimeUnit.SECONDS);

    }


    private long getRate(long ttl) {
        if (ttl - 5 > 0) {
            return ttl - 5;
        } else if (ttl - 1 > 0) {
            return ttl - 1;
        }
        throw new RuntimeException("ttl 不允许小于1");
    }



    private String  getLuaScript(String fileName){

        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
        InputStreamReader reader = new InputStreamReader(resourceAsStream);

        try {
            return CharStreams.toString(reader);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


    class WatcherThread implements Runnable{

        private ScheduledThreadPoolExecutor executor;

        private List<String> key;

        private List<String> args;

        public WatcherThread(ScheduledThreadPoolExecutor executor, List<String> key, List<String> args) {
            this.executor = executor;
            this.key = key;
            this.args = args;
        }

        @Override
        public void run() {
            log.info("续期操作开始");
            Jedis jedis = jedisThreadLocal.get();
            Long eval = (Long)jedis.eval(getLuaScript(watcher), key, args);
            log.info("续期结果值：{}",eval);
            if(eval==0){
                executor.shutdown();
            }

        }
    }

}
